package com.example.starter.webmvc.handler;

import cn.hutool.core.util.ObjUtil;
import com.example.starter.exception.BusinessException;
import com.example.starter.util.ResponseEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 统一异常处理
 * </p>
 *
 * @author 王令
 * @since 2022-05-13 15:14:14
 */
@Order
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
public class GlobalExceptionHandler<T> implements ResponseBodyAdvice<ResponseEntity<T>> {

    @Value("${com.example.mvc.error.throwable:true}")
    private boolean throwable;
    @Value("${com.example.mvc.error.err-msg:系统异常}")
    private String defaultErrorMsg;

    /**
     * 判断是否要执行beforeBodyWrite方法，true为执行，false不执行
     */
    @Override
    public boolean supports(MethodParameter returnType,
            @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return ResponseEntity.class.isAssignableFrom(Objects.requireNonNull(returnType.getMethod()).getReturnType());
    }

    /**
     * 对response处理的执行方法
     */
    @Override
    public ResponseEntity<T> beforeBodyWrite(ResponseEntity<T> responseEntity,
            @NonNull MethodParameter returnType,
            @NonNull MediaType mediaType,
            @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
            @NonNull ServerHttpRequest request,
            @NonNull ServerHttpResponse response) {
        // 返回数据处理
        if (ObjUtil.isNotNull(responseEntity)) {
            responseEntity.setTimestamp(System.currentTimeMillis());
            response.setStatusCode(HttpStatus.resolve(responseEntity.getCode()));
        }
        return responseEntity;
    }


    @ExceptionHandler(Exception.class)
    public ResponseEntity<Void> exception(Exception e) {
        log.error(e.getLocalizedMessage());
        if (throwable) {
            return ResponseEntity.error(e.getLocalizedMessage());
        } else {
            return ResponseEntity.error(defaultErrorMsg);
        }
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<Void> methodNotAllowed(HttpRequestMethodNotSupportedException e) {
        log.error(e.getLocalizedMessage());
        return ResponseEntity.<Void>builder()
                .code(HttpStatus.METHOD_NOT_ALLOWED.value())
                .message(e.getLocalizedMessage())
                .build();
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<Void> notFound404(NoHandlerFoundException e) {
        log.error(e.getLocalizedMessage());
        return ResponseEntity.fail(HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(BindException.class)
    public ResponseEntity<Void> bindException(BindException e) {
        log.error(e.getLocalizedMessage());
        return ResponseEntity.fail("参数校验失败: " + e
                .getBindingResult()
                .getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(", ")));
    }

    @ExceptionHandler({
            BusinessException.class
    })
    public ResponseEntity<Void> businessException(BusinessException e) {
        log.error(e.getLocalizedMessage());
        return ResponseEntity.fail(e);
    }

    @ExceptionHandler({
            RuntimeException.class
    })
    public ResponseEntity<Void> runtimeException(RuntimeException e) {
        log.error(e.getLocalizedMessage());
        return ResponseEntity.fail(e);
    }

}
